
/******************************************
Shaoni Sinha (shaoni@uw.edu)
Description: Client side code
******************************************/

package rpc_client_server;

import java.io.File;
import java.io.FileWriter;
import java.rmi.NotBoundException;
import java.rmi.RMISecurityManager;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

public class Client {
	
	/** Number of threads in the client threadpool */
	private static int NTHREADS = 1;
	
	/** Range of key-value indices 0 <= index < NREQUESTS. */
	private static int NREQUESTS = 5;
	
	/** Range of key-value indices 0 <= index < NREQUESTS. */
	private static int NUMOFSERVERS = 5;
	
	/** Timeout for receiving the server response. */
	private static int TIMEOUT_IN_SECS = 10;
	
	private static File clientLog;
	private static ExecutorService executor;
	private static List<Future<String>> listOfServerResponse;
	
	static RemoteKeyValue[] stubs = new RemoteKeyValue[5];
	static Registry[] theReg = new Registry[5];
	
	/** The local proxy for the service. */
	protected static RemoteKeyValue netConn = null;
	
	public static void main(String[] args) throws RemoteException, NotBoundException
	{
		/** We insert the key-value pair only through S0. Then GET from all servers. Finally DELETE from S4. */
		
		/** Initialize the stubs */
		for(int i = 0; i < NUMOFSERVERS; i++){
			int port = 10000 + i;
			theReg[i] =  LocateRegistry.getRegistry("localhost", port);
			stubs[i] = (RemoteKeyValue) theReg[i].lookup("RemoteKeyValue");
		}
		
		/** Initialize the client log */
		clientLog = new File("clientlog.txt");
		
		/** Optionally we have a SecurityManager for downloading of classes: */
		System.setSecurityManager(new RMISecurityManager());
		
		try {
			executor = Executors.newFixedThreadPool(NTHREADS);
			listOfServerResponse = new ArrayList<Future<String>>();
		} catch (Exception e) {
			System.err.println("RemoteKeyValue exception: " + e.getMessage());
			e.printStackTrace();
		}
		
		/** Populate the key-value store */
		populateList(stubs[0]);
		
		/** Wait for sometime. */
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e1) {
			e1.printStackTrace();
		}
		
		/** Try getting the key from all servers */
		for(int i = 0; i < NUMOFSERVERS; i++)
		{
		    /** Get requests. */
			getRequests(stubs[i]);
		}
		
		/** Wait for sometime. */
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e1) {
			e1.printStackTrace();
		}
		
	    /** Delete requests. */
		deleteRequests(stubs[NUMOFSERVERS-1]);
	    
	    /** Retrieve the result */
	    for (Future<String> future : listOfServerResponse) {
	      try {
	    	  String serverResponse = future.get(TIMEOUT_IN_SECS, TimeUnit.SECONDS); 
	    	  if(!checkResponse(serverResponse)){
	    		  UpdateClientLog("Received unsolicited response. \r\n");  
	    	  } 
	    	  else
	    	  {
	    		  UpdateClientLog(serverResponse + "\r\n");
	    	  }
	      } catch (InterruptedException e) {
	          e.printStackTrace();
	      } catch (ExecutionException e) {
	    	  e.printStackTrace();
	      } catch (TimeoutException e) {
	    	  UpdateClientLog("Unresponsive server \r\n");
	      }
	    }
	    
	    executor.shutdown();
	    System.out.println(Client.GetTimestamp() + " ... Client is done.");
	}
	
	private static void populateList(final RemoteKeyValue theStub)
	{
		/**Populate the list.*/
	    for (int i = 0; i < NREQUESTS; i++) {
	    	final String key = "k" + i;
			final String value = "v" + i;
		    Callable<String> worker = new  Callable<String>() {
	            @Override
	            public String call() throws Exception {
	              String serverResponse = theStub.putKeyValue(key, value);
	              return serverResponse;
	            }
	          };
	        Future<String> submit= executor.submit(worker);
	        listOfServerResponse.add(submit);
	    }
	}
	
	private static void getRequests(final RemoteKeyValue theStub)
	{
	    for (int i = 0; i < NREQUESTS; i++) {
	    	final String key = "k" + i;
			Callable<String> worker = new  Callable<String>() {
	            @Override
	            public String call() throws Exception {
	              String serverResponse = theStub.getValue(key);
	              return serverResponse;
	            }
	          };
	        Future<String> submit= executor.submit(worker);
	        listOfServerResponse.add(submit);
	    }

	}
	
	private static void deleteRequests(final RemoteKeyValue theStub)
	{
		for (int i = 0; i < NREQUESTS; i++) {
	    	final String key = "k" + i;
			Callable<String> worker = new  Callable<String>() {
	            @Override
	            public String call() throws Exception {
	              String serverResponse = theStub.deleteKeyValue(key);
	              return serverResponse;
	            }
	          };
	        Future<String> submit= executor.submit(worker);
	        listOfServerResponse.add(submit);
	    }
	}
	
	private static boolean checkResponse(String theResponse){
		boolean isSolicited = false;
		String prefixForDelete = "Key-Value pair corresponding to key-k";
		
		if(theResponse == null)
		{
			System.out.println("Null server response");
			return isSolicited;
		}
		
		/** Handles timeout and PUT request. */
		if((theResponse.startsWith("Key-Value pair added to KV Store!"))) 
		{
			isSolicited = true;
		}
		/** Handles GET request. */
		else if(theResponse.startsWith("v")) 
		{
			String value = theResponse.substring(1);
			int index = Integer.parseInt(value);
			if(index >= 0 && index < NREQUESTS)
				isSolicited = true;
		}
		/** Handles DELETE request. */
		else if(theResponse.startsWith(prefixForDelete)) 
		{
			String value = theResponse.substring(prefixForDelete.length());
			String [] args = value.split(" ");
			if(args.length > 1)
			{
				int index = Integer.parseInt(args[0]);
				if(index >= 0 && index < NREQUESTS)
					isSolicited = true;
			}
		}
			
		return isSolicited;
	}
	
	/* Create a timestamp. */    
	private static Timestamp GetTimestamp() {
		Date date= new java.util.Date();
		return (new Timestamp(date.getTime()));
	}
	
	/* Update client log. */
	private static void UpdateClientLog(String errString) {
		try {
			FileWriter fw = new FileWriter(clientLog, true);
			Timestamp ts = Client.GetTimestamp();
			String log = ts + " ... " + errString;
			System.out.print(log);
			fw.write(log);
			fw.close();
		} catch (Exception e) {
			System.out.println(GetTimestamp() + " ... Failure to update clientlog!");
			e.printStackTrace();
		}
		return;
	}
}

